#ident	"@(#)Main.cc	1.2	AKK/CII	20090222"

#include <iostream>
#include <cmath>
using std::cout;
using std::cerr;
using std::endl;

#include <setjmp.h>
#include <signal.h>
#include <getopt.h>

#include "asserts.h"

#include "../Opdracht/BuddyAllocator.h"
#include "../Opdracht/FreeListAllocator.h"
#include "../Opdracht/BestFit.h"

Allocator  * allocator[] =
{
    new BuddyAllocator(),
#define	BD_ALLOC	0

     new FreeListAllocator(),
#define	P2_ALLOC	1
    
    new BestFit(),
#define BF_ALLOC    2
};


// ===================================================================

// globale, hulp functie
inline		// NB deze 'inline' is alleen wat optimalisatie
bool	vraagkans( int r )
{
    return (((r >> 5) % 16) < 10);
    // De "vraag" kans is 10 op 16, maar we kijken naar een paar
    // bitjes "halverwege" de gegeven r en niet naar de "laatste".
    // >> = schuif bitjes naar rechts
    // %  = modulo = rest na deling
}


// ======================================================

#include "Main.h"	// De pseudo applicatie


Main::Main( Allocator * beheerder, int size )
    : beheerder(beheerder), size(size)
{
    // nooit iets geloven ...
    require( beheerder != 0 );
    require( size > 0 );
}


// actie: na afloop alles netjes opruimen.
Main::~Main()
{
    // zijn er nog objecten over?
    while (!objecten.empty()) {
	vergeetOudste();
    }
}


// actie: vraag om geheugen (onze versie van 'new')
void	Main::vraagGeheugen( int omvang )
{
    cout <<"Vraag "<< omvang <<"\t";

    // Deze controles overslaan als we aan het testen zijn.
    if (!tflag)
	require( (0 < omvang) && (omvang <= size) );	// is 'omvang' wel geldig ?

    const Area  * ap = beheerder->alloc( omvang );

    if (ap != 0x0)
    {
        cout <<"kreeg gebied "<< ap->getBase() <<"..."<< ap->getEnd() <<":"<< ap->getSize() <<endl;

        // Het gekregen gebied moeten we natuurlijk wel onthouden
        objecten.push_back( const_cast<Area*>(ap) );
        // NB De 'push_back' schijnt niet te houden van 'const'

        // En nou moeten we controlen of er geen overlap
        // bestaat met wat we al eerder hadden gekregen ...
        ALIterator	i;
        for ( i = objecten.begin() ; i != objecten.end() ; i++ )
        {
            const Area  * xp = *i;

            if (((xp->getBase() <= ap->getBase()) && (ap->getBase() <= xp->getEnd())) || ((xp->getBase() <= ap->getEnd()) && (ap->getEnd()  <= xp->getEnd())))
            {
                if (ap != xp)
                {
                    // Dit zou eigenlijk een "fatal error" moeten zijn,
                    // maar bij de RandomFit zal dit wel vaker gebeuren
                    // dus voorlopig alleen maar melden dat het fout is ...
                    cout <<"Oeps, nieuwe gebied overlapt met "<< xp->getBase()
                                <<"..."<< xp->getEnd() <<":"<< xp->getSize() <<endl;
                    break;	// verder zoeken is niet meer zo zinvol ...
                        // ... levert alleen maar meer uitvoer op.
                }
            }
        }
        // ... bedenk zelf maar of er misschien nog andere dingen zijn om te controleren
    }
    else 
    {
        //std::cout << "Oeps, geheugen is op" << std::endl;
    }
}


// actie: geef een gekregen gebied weer terug (onze versie van 'delete')
void	Main::vergeetOudste()
{
    require( ! objecten.empty() );		// hebben we eigenlijk wel wat ?
    const Area  * ap = objecten.front();	// het oudste gebied opzoeken, ...
    cout <<"Retour "<< ap->getBase() <<"..."<< ap->getEnd() <<":"<< ap->getSize() <<endl;
    objecten.pop_front();			// ... uit de lijst halen
    beheerder->free( ap );			// ... en vrij geven
}


// =========== ALGORITME METEN =============

// zomaar wat onzin acties
// bedenk zelf wat zinvollers
void	Main::randomscenario( int aantal )
{
    // eerst even de random generator wat opschudden
#ifndef	unix		// voor devcpp onder windows,
    srand( time( 0 ) );	// windows heeft geen getpid'
#else
    srand( time( 0 ) ^ getpid() );
#endif

    // doe tig-keer "iets"
    for (int x = 0 ; x<aantal; x++)
    {
        int r = rand();

        if (objecten.empty() || vraagkans(r))	
        {
            float expscale = std::pow((float)rand()/(float)RAND_MAX, 2.0f);
            vraagGeheugen((int)(1 + (expscale*(size-2))));
        }
        else if (!objecten.empty())
        {
            vergeetOudste();
        }
        else 
        {
            std::cout << "Niets" << std::endl;
        }
    }
    
    while (!objecten.empty())
    {
        vergeetOudste();
    }
    
    beheerder->printFragmentation();
}


// =========== ALGORITME TESTEN ==========

// Elke test die hieronder uitgevoerd wordt
// zou een 'assert' failure moeten veroorzaken
// die de normale executie volgorde afbreekt.
// Die "exception" wordt op zijn beurt afgevangen door 'onAbort'
// waarna we weer terug komen in deze functie (dankzij setjmp/longjmp)
// en de volgende test kunnen doen.
// Eigenlijk is dit wat er "onder tafel" gebeurt bij "try ... catch".


jmp_buf	 jmpbuf;	// buffer voor "executie bookmark" (setjmp/longjmp)
// NB Dit moet een globale variabele zijn
// omdat 'onAbort' er ook bij moet kunnen.


void	Main::testing()
{
    int  fase = 0;		// Houdt bij hoe ver we zijn in de test procedure.

    tflag = true;		// Leg 'vraagGeheugen' even het zwijgen op.

    setjmp( jmpbuf );		// Maak een "executie bookmark" voor "hier"
    				// HINT: Denk aan "try/catch" in Java en C++.
				// Na elke 'test' komen we weer hier terug
				// voor de volgende test.

    cerr <<"Stap "<< (fase++) <<":\n";

    switch (fase)
    {
	case 1:
	    cerr <<"Om niets vragen ...\n";
	    vraagGeheugen( 0 );		// dit mag niet kunnen ...
	    // ... dus hier zouden we niet mogen komen ...
	    cerr <<"FAILED\n";		// ... maar de controle heeft gefaald
	    /*FALLTHRU*/
	case 2:
	    cerr <<"Te veel vragen ...\n";
	    vraagGeheugen( size + 1 );
	    cerr <<"FAILED\n";
	    /*FALLTHRU*/
	case 3:
	    cerr <<"Te weinig vragen ...\n";
	    vraagGeheugen( -1 );
	    cerr <<"FAILED\n";
	    /*FALLTHRU*/
	case 4:
	    cerr <<"Om alles vragen ...\n";
	    vraagGeheugen( size );	// PAS OP: dit mag dus wel!
	    cerr <<"OKE\n";
	    /*FALLTHRU*/
	case 5:
		// voeg zonodig nog andere testcases toe
	default:
	    cerr <<"Einde code testen\n";
	    tflag = false;	// voor het geval dat,
	    			// dit voorkomt een oneindige lus.
	    break;
    }
}



// Functie voor software interrupt afhandeling.
void	onAbort( int signo )
{
    // Een z.g. "forward declaration"
    extern bool	tflag;	// De echte declaratie staat verderop

    cerr <<"Caught signal "<< signo <<"...";

    // Zijn we aan het testen ?  ...
    if (tflag && (signo == SIGABRT))
    {
	// .. dan was dit "te verwachten".
	cerr <<"GOOD.\n";
    	longjmp( jmpbuf, 1 );	// ga terug naar de "executie bookmark"
				// HINT: Denk aan "throw" in Java.
    } else
	// Niet? dan is er echt iets fout gegaan
	cerr <<"Eh?\n";

#ifndef	unix		// voor devcpp onder windows,
    system("PAUSE");	// anders is je venster meteen weg.
#endif
    exit(1);
}


// ===================================================================
// Het hoofdprogramma.
// Handel opties af, kies beheerder, enz
// en dan gaan we wat spelen.

// Globale hulpvariabelen voor main en doOptions
Allocator  * beheerder = 0;	// de gekozen Allocator
int	     size = 1024;	// de omvang van het nep geheugen
int	     aantal = 1000;	// hoe vaak doen we het ...
bool	     tflag;		// true als we de code willen "testen"
				// anders wordt er "gemeten".


// Analyseert de meegegeven opties.
// Kan/zal bovenstaande globale variabelen veranderen !
void	doOptions( int argc, char * argv[] )
{
    char  options[] = "s:a:trbf2";	// de opties die we willen herkennen
				// s: voor: -s xxx = omvang nepgeheugen
				// a: voor: -a xxx = aantal acties
				// t  voor: -t = code testen (i.p.v. performance meten)
				//
				// Voor de beheeralgoritmes bieden we ...
				// r  voor: -r = RandomFit allocator
			// Als je alle algoritmes zou bouwen dan wordt
			// het zoiets als "s:a:trfnbwpm2"
    				// f  voor: -f = first-fit
				// n  voor; -n = next-fit
				//  enz
				// 2  voor: -2 = buddy
			// Voor meer informatie: zie man 3 getopt

    int  opt = 0;	// "huidige" optie karakter
    do {

	opt = getopt( argc, argv, options );	// de uitpluizer

	switch (opt)		// welke optie hebben we gezien ...
	{
	    case 's':	// -s xxx = size
		size = atol( optarg );		// atol = "ascii to long"
		break;

	    case 'a':	// -a xxx = aantal
		aantal = atol( optarg );	// atol = "ascii to long"
		break;

	    case 't':	// -t = code testen
	    	tflag = true;
		break;
            
        case 'b':
            beheerder = allocator[BF_ALLOC];
        break;
            
        case 'f':
            beheerder = allocator[P2_ALLOC];
        break;
            
        case '2':
            beheerder = allocator[BD_ALLOC];
        break;

	    case -1:	// = einde opties
		break;

	    default:	// eh? iets onbekends gevonden
		// foutmelding geven ...
		cerr <<"Usage: "<< argv[0] <<", valid options are "<< options <<"\n";
		// en ermee kappen
#ifndef	unix				// voor DevCPP onder windows,
		system("PAUSE");	// anders is je venster meteen weg.
#endif
		exit(1);		// 1 = "something went wrong"
	}
    } while (opt != -1); // tot einde opties
}



// Programma executie begint hier ...
int  main( int argc, char * argv[] )
{
    signal(SIGABRT, onAbort);	
    doOptions( argc, argv );

    if (beheerder)
    {
        check( size > 0 );
        beheerder->setSize( size );

        cerr <<"Using "<< beheerder->getType() <<" "<< aantal <<" times on "<< size <<" bytes\n";

        Main  * mp = new Main( beheerder, size );

        if (tflag)
            mp->testing();
        else
            mp->randomscenario( aantal );

        delete mp;
        delete beheerder;
    } 
    else 
    {
        cerr <<"Oeps, geen beheerder gekozen ....\n";
    }

    return 0;
}
